#!/usr/bin/env python
# encoding: utf-8
"""
DjangoBlog 工具函数模块
提供项目通用的工具函数，包括缓存、加密、Markdown处理、邮件发送等
这些函数在整个项目中多处被调用，是核心的基础设施
"""

import logging
import os
import random
import string
import uuid
from hashlib import sha256

import bleach
import markdown
import requests
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.templatetags.static import static

# 配置日志记录器，用于记录工具函数的操作日志
logger = logging.getLogger(__name__)


def get_max_articleid_commentid():
    """
    获取最大的文章ID和评论ID
    用于生成新的ID或统计信息
    
    返回:
        tuple: (最新文章ID, 最新评论ID)
    """
    from blog.models import Article
    from comments.models import Comment
    return (Article.objects.latest().pk, Comment.objects.latest().pk)


def get_sha256(str):
    """
    计算字符串的SHA256哈希值
    用于密码加密、签名验证等安全场景
    
    参数:
        str: 要计算哈希的字符串
        
    返回:
        str: 64位的十六进制哈希值
    """
    m = sha256(str.encode('utf-8'))  # 将字符串编码为UTF-8后计算哈希
    return m.hexdigest()  # 返回十六进制表示的哈希值


def cache_decorator(expiration=3 * 60):
    """
    缓存装饰器工厂函数
    为函数添加缓存功能，提高性能，减少重复计算
    
    参数:
        expiration: 缓存过期时间，默认3分钟
        
    返回:
        decorator: 缓存装饰器
    """
    def wrapper(func):
        def news(*args, **kwargs):
            """
            包装后的函数，实现缓存逻辑
            """
            try:
                # 尝试从视图对象获取缓存键（如果被装饰的是类方法）
                view = args[0]
                key = view.get_cache_key()
            except:
                key = None  # 如果不是视图方法，key为None
            
            if not key:
                # 生成基于函数和参数的唯一缓存键
                unique_str = repr((func, args, kwargs))  # 将函数和参数转换为字符串表示
                m = sha256(unique_str.encode('utf-8'))  # 计算哈希作为缓存键
                key = m.hexdigest()
            
            # 尝试从缓存获取值
            value = cache.get(key)
            if value is not None:
                # 缓存命中
                # logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key))
                if str(value) == '__default_cache_value__':
                    # 特殊值，表示原函数返回None
                    return None
                else:
                    return value  # 返回缓存的值
            else:
                # 缓存未命中，执行原函数并设置缓存
                logger.debug(
                    'cache_decorator set cache:%s key:%s' %
                    (func.__name__, key))
                value = func(*args, **kwargs)  # 执行被装饰的函数
                
                # 处理None值，使用特殊标记存储
                if value is None:
                    cache.set(key, '__default_cache_value__', expiration)
                else:
                    cache.set(key, value, expiration)  # 设置缓存
                return value

        return news

    return wrapper


def expire_view_cache(path, servername, serverport, key_prefix=None):
    '''
    刷新视图缓存
    当数据更新时，手动使特定视图的缓存失效
    
    参数:
        path: URL路径
        servername: 主机名
        serverport: 端口
        key_prefix: 缓存键前缀
        
    返回:
        bool: 是否成功删除缓存
    '''
    from django.http import HttpRequest
    from django.utils.cache import get_cache_key

    # 创建模拟请求对象，用于生成缓存键
    request = HttpRequest()
    request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport}
    request.path = path

    # 获取缓存键
    key = get_cache_key(request, key_prefix=key_prefix, cache=cache)
    if key:
        logger.info('expire_view_cache:get key:{path}'.format(path=path))
        if cache.get(key):
            cache.delete(key)  # 删除缓存
        return True
    return False


@cache_decorator()  # 应用缓存装饰器，缓存当前站点信息
def get_current_site():
    """
    获取当前站点信息
    使用缓存提高性能，避免频繁查询数据库
    
    返回:
        Site: 当前站点对象
    """
    site = Site.objects.get_current()  # 获取Django站点框架中的当前站点
    return site


class CommonMarkdown:
    """
    Markdown处理工具类
    提供Markdown文本到HTML的转换功能，支持代码高亮、目录生成等
    """
    
    @staticmethod
    def _convert_markdown(value):
        """
        内部方法：执行Markdown转换
        配置Markdown扩展，支持额外功能
        
        参数:
            value: Markdown格式的文本
            
        返回:
            tuple: (转换后的HTML, 生成的目录)
        """
        # 配置Markdown扩展
        md = markdown.Markdown(
            extensions=[
                'extra',      # 额外功能（表格、脚注等）
                'codehilite', # 代码语法高亮
                'toc',        # 自动生成目录
                'tables',     # 表格支持
            ]
        )
        body = md.convert(value)  # 转换Markdown为HTML
        toc = md.toc  # 提取生成的目录
        return body, toc

    @staticmethod
    def get_markdown_with_toc(value):
        """
        获取带目录的Markdown转换结果
        适用于需要显示文章目录的场景
        
        参数:
            value: Markdown格式的文本
            
        返回:
            tuple: (HTML内容, 目录HTML)
        """
        body, toc = CommonMarkdown._convert_markdown(value)
        return body, toc

    @staticmethod
    def get_markdown(value):
        """
        获取不带目录的Markdown转换结果
        适用于不需要目录的简单转换
        
        参数:
            value: Markdown格式的文本
            
        返回:
            str: 转换后的HTML
        """
        body, toc = CommonMarkdown._convert_markdown(value)
        return body


def send_email(emailto, title, content):
    """
    发送邮件信号触发器
    实际邮件发送通过信号机制处理，实现解耦
    
    参数:
        emailto: 收件人邮箱列表
        title: 邮件标题
        content: 邮件内容
    """
    from djangoblog.blog_signals import send_email_signal
    send_email_signal.send(
        send_email.__class__,
        emailto=emailto,
        title=title,
        content=content)


def generate_code() -> str:
    """生成6位随机数字验证码，用于邮箱验证等场景"""
    return ''.join(random.sample(string.digits, 6))  # 从数字中随机选择6个字符


def parse_dict_to_url(dict):
    """
    将字典转换为URL查询字符串
    用于构建URL参数，自动进行URL编码
    
    参数:
        dict: 要转换的字典
        
    返回:
        str: URL查询字符串
    """
    from urllib.parse import quote
    url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/'))
                    for k, v in dict.items()])  # 对键值对进行URL编码并拼接
    return url


def get_blog_setting():
    """
    获取博客设置信息
    使用缓存优化性能，避免频繁查询数据库
    如果数据库中没有设置，创建默认设置
    
    返回:
        BlogSettings: 博客设置对象
    """
    value = cache.get('get_blog_setting')  # 尝试从缓存获取
    if value:
        return value
    else:
        from blog.models import BlogSettings
        # 如果数据库中没有设置记录，创建默认设置
        if not BlogSettings.objects.count():
            setting = BlogSettings()
            setting.site_name = 'djangoblog'
            setting.site_description = '基于Django的博客系统'
            setting.site_seo_description = '基于Django的博客系统'
            setting.site_keywords = 'Django,Python'
            setting.article_sub_length = 300
            setting.sidebar_article_count = 10
            setting.sidebar_comment_count = 5
            setting.show_google_adsense = False
            setting.open_site_comment = True
            setting.analytics_code = ''
            setting.beian_code = ''
            setting.show_gongan_code = False
            setting.comment_need_review = False
            setting.save()
        value = BlogSettings.objects.first()  # 获取第一条设置记录
        logger.info('set cache get_blog_setting')
        cache.set('get_blog_setting', value)  # 设置缓存
        return value


def save_user_avatar(url):
    '''
    下载并保存用户头像到本地
    用于第三方登录时获取用户头像
    
    参数:
        url: 头像的远程URL地址
        
    返回:
        str: 本地头像的静态URL路径
    '''
    logger.info(url)

    try:
        # 构建本地保存目录
        basedir = os.path.join(settings.STATICFILES, 'avatar')
        # 下载远程头像文件
        rsp = requests.get(url, timeout=2)
        if rsp.status_code == 200:  # 请求成功
            if not os.path.exists(basedir):
                os.makedirs(basedir)  # 创建目录

            # 检查文件是否为图片
            image_extensions = ['.jpg', '.png', 'jpeg', '.gif']
            isimage = len([i for i in image_extensions if url.endswith(i)]) > 0
            # 确定文件扩展名
            ext = os.path.splitext(url)[1] if isimage else '.jpg'
            # 生成唯一文件名
            save_filename = str(uuid.uuid4().hex) + ext
            logger.info('保存用户头像:' + basedir + save_filename)
            # 保存文件到本地
            with open(os.path.join(basedir, save_filename), 'wb+') as file:
                file.write(rsp.content)
            return static('avatar/' + save_filename)  # 返回静态URL
    except Exception as e:
        logger.error(e)  # 记录错误日志
        return static('blog/img/avatar.png')  # 返回默认头像


def delete_sidebar_cache():
    """
    删除侧边栏相关缓存
    当侧边栏内容更新时调用，确保显示最新内容
    """
    from blog.models import LinkShowType
    keys = ["sidebar" + x for x in LinkShowType.values]  # 生成所有侧边栏缓存键
    for k in keys:
        logger.info('delete sidebar key:' + k)
        cache.delete(k)  # 删除缓存


def delete_view_cache(prefix, keys):
    """
    删除特定视图的模板片段缓存
    用于手动刷新缓存内容
    
    参数:
        prefix: 缓存前缀
        keys: 缓存键的变量值
    """
    from django.core.cache.utils import make_template_fragment_key
    key = make_template_fragment_key(prefix, keys)  # 生成模板片段缓存键
    cache.delete(key)  # 删除缓存


def get_resource_url():
    """
    获取静态资源的基础URL
    根据配置返回相应的静态文件URL
    
    返回:
        str: 静态资源基础URL
    """
    if settings.STATIC_URL:  # 如果配置了STATIC_URL
        return settings.STATIC_URL
    else:
        # 否则基于当前站点构建URL
        site = get_current_site()
        return 'http://' + site.domain + '/static/'


# HTML净化相关配置
ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1',
                'h2', 'p']  # 允许的HTML标签
ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']}  # 允许的HTML属性


def sanitize_html(html):
    """
    净化HTML内容，防止XSS攻击
    移除不安全的标签和属性，只保留安全的HTML
    
    参数:
        html: 要净化的HTML字符串
        
    返回:
        str: 净化后的安全HTML
    """
    return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES)